﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.Globalization;
using System.ComponentModel;
using System.Web;
using System.IO;
using System.Reflection;
using System.Collections.Specialized;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;

namespace JQuery.Net.UI
{
    public delegate void DataRequestHandler(object sender, jQNetEventArgs args);

    [ParseChildren(true), PersistChildren(false)]
    public abstract class jQNetControl
        : Control
        , IjQNetControl
        , IAttributeAccessor
    {
        #region private data
        private string tag;
        private bool postdata_loaded = false;
        private jQNetAttributeCollection attributes;
        private Templates templates;
        protected ScriptContainer Scripts = null;
        #region event data keys
        private static readonly object onDataRequest = new object();
        #endregion
        #region field constants
        protected const string undefined = "undefined";
        protected const string error = @"
        function(d, e, x) {
            alert(e);
        }";
        #endregion
        #endregion

        #region constructors
        public jQNetControl()
            : base()
        {
            HTMLTagKey = HtmlTextWriterTag.Div;
            SourceType = DataSourceType.local;
            Enabled = true;
            EnableViewState = false;
            Templates = new List<Script>();
            ScriptEvents = new List<Script>();
            /// fields
            attributes = new jQNetAttributeCollection(this);
            Scripts = new ScriptContainer(this);
        }

        public jQNetControl(HtmlTextWriterTag oTagKey)
            : this()
        {
            HTMLTagKey = oTagKey;
        }

        public jQNetControl(string oTag)
            : this()
        {
            tag = oTag.ToLower(CultureInfo.InvariantCulture);
        }
        #endregion

        #region event handler(s)
        /// <summary>
        /// Add or Remove the Data Request Event Handler
        /// </summary>
        public event DataRequestHandler DataRequest
        {
            add
            {
                lock (onDataRequest)
                {
                    Events.AddHandler(onDataRequest, value);
                }
            }
            remove
            {
                lock (onDataRequest)
                {
                    Events.RemoveHandler(onDataRequest, value);
                }
            }
        }
        #endregion

        #region properties
        protected HtmlTextWriterTag HTMLTagKey
        {
            get
            {
                HtmlTextWriterTag Tag = HtmlTextWriterTag.Unknown;

                if (!Tag.TryParse<HtmlTextWriterTag>(tag, ref Tag))
                    Tag = HtmlTextWriterTag.Unknown;

                return Tag;
            }
            set
            {
                tag = Enum.GetName(typeof(HtmlTextWriterTag), value).ToLower(CultureInfo.InvariantCulture);
            }
        }

        public string HTMLTag { get { return tag; } }

        protected Templates RenderTemplates
        {
            get
            {
                if (templates == null)
                {
                    templates = new Templates(this);
                }
                return templates;
            }
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public virtual List<Script> Templates { get; set; }
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public virtual List<Script> ScriptEvents { get; set; }

        [DisplayName("Source Type"), Description("Determines how the data that makes up the menu is delivered to the client."), Category("Behavior"), Browsable(true)]
        public virtual DataSourceType SourceType { get; set; }
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public jQNetAttributeCollection Attributes { get { return attributes; } }
        [Category("Appearance")]
        [CssClassProperty]
        [DefaultValue("")]
        public virtual string CssClass { get { return attributes["class"]; } set { attributes["class"] = value; } }
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public CssStyleCollection Style { get { return attributes.CssStyles; } }
        [Browsable(false), Bindable(false)]
        protected jQNetArguments Arguments { get; set; }
        [Browsable(true), Bindable(true)]
        public bool Enabled { get; set; }
        #endregion

        #region method(s)
        protected virtual void OnDataRequest(jQNetEventArgs oArgs)
        {
            DataRequestHandler oHandler = null;

            lock (onDataRequest)
            {
                oHandler = (DataRequestHandler)Events[onDataRequest];
            }

            if (oHandler != null)
            {
                oHandler(this, oArgs);
            }
        }

        protected virtual void RenderBeginTag(HtmlTextWriter oWriter)
        {
            oWriter.WriteBeginTag(HTMLTag);

            Attributes["id"] = ClientID;
            Attributes["name"] = UniqueID;

            if (Enabled == false)
            {
                Attributes["disabled"] = "disabled";
            }

            Attributes.Render(oWriter);

            if (HTMLTagKey != HtmlTextWriterTag.Input && HTMLTagKey != HtmlTextWriterTag.Br && HTMLTagKey != HtmlTextWriterTag.Hr)
            {
                oWriter.Indent++;

                oWriter.WriteLine(" >");
            }
        }

        protected virtual void RenderEndTag(HtmlTextWriter writer)
        {
            if (HTMLTagKey != HtmlTextWriterTag.Input && HTMLTagKey != HtmlTextWriterTag.Br && HTMLTagKey != HtmlTextWriterTag.Hr)
            {
                writer.Indent--;
                writer.WriteLine();
                writer.WriteLine(String.Format("</{0}>", HTMLTag));
            }
            else
            {
                writer.WriteLine(" />");
            }
        }

        protected virtual void RenderItemTemplate<T>(HtmlTextWriter oWriter, T oItem, bool isTemplate)
            where T : class
        {

        }

        protected virtual string PrefixTemplateID(string id)
        {
            return id;
        }

        protected void EnsureDefaultValues(Func<PropertyInfo, object[], bool> property)
        {
            foreach (PropertyInfo oProperty in GetType().GetProperties())
            {
                object[] attributes = null;

                if ((attributes = oProperty.GetCustomAttributes(typeof(DefaultValueAttribute), false)).Length == 0 || oProperty.GetValue(this, null) != null)
                {
                    continue;
                }

                if (!property(oProperty, attributes))
                    oProperty.SetValue(this, ((DefaultValueAttribute)attributes[0]).Value, null);
            }
        }

        protected virtual void EnsureDefaultValues()
        {
            EnsureDefaultValues((X,Y) => false);  /// utilize default behavior
        }

        #region method override(s)
        protected override void OnInit(EventArgs e)
        {
            Page.InitComplete += new EventHandler(Page_InitComplete);

            EnsureDefaultValues();
        }

        void Page_InitComplete(object sender, EventArgs e)
        {
            if (SourceType != DataSourceType.local && Events[onDataRequest] == null)
            {
                throw new InvalidOperationException(String.Format("{1}'s Source Type has been initialized to {0} and does not implement the Data Request Event Handler!", SourceType, ID));
            }

            if (Templates.Count > 0)
            {
                Templates.ForEach((T) =>
                {
                    if (T.Type == jQNetScriptType.html)
                    {
                        T.ID = PrefixTemplateID(T.ID);

                        RenderTemplates[T.ID] = new Template()
                        {
                            ID = T.ID,
                            Script = T.Body,
                            Type = String.Format("text/{0}", T.Type)
                        };
                    }
                });
            }

            if (ScriptEvents.Count > 0)
            {
                ScriptEvents.ForEach((S) =>
                {
                    if (S.Type == jQNetScriptType.javascript)
                    {
                        Scripts[S.Event] = S.Body;
                    }
                });
            }
        }

        protected override void OnLoad(EventArgs e)
        {
            jQNetEventArgs oArgs = new jQNetEventArgs();

            OnDataRequest(oArgs);

        }

        protected override void Render(HtmlTextWriter writer)
        {
            RenderBeginTag(writer);
            base.Render(writer);
            RenderEndTag(writer);
        }
        #endregion
        #endregion

        #region IjQNetControl Interface Implementation
        /// <summary>
        /// Register the Script Includes that are required for the UI Core.
        /// </summary>
        public virtual void RegisterScriptIncludes(JQNRegistration options)
        {
        }
        /// <summary>
        /// Register the startup script necesary for the initialization of this control
        /// </summary>
        /// <param name="writer"></param>
        public abstract void RegisterStartupScript(StringWriter writer);
        #endregion

        #region "IPostBackDataHandler Members"
        public bool LoadPostData(string postDataKey, NameValueCollection collection)
        {
            NameValueCollection data = new NameValueCollection();

            foreach (string name in collection.AllKeys)
            {
                if (name.StartsWith(postDataKey))
                {   /// filter the form data from the collection and data that prefix's with this control stuff it into a new collection
                    data.Add(name, collection[name]);
                }
            }

            return ((postdata_loaded = ProcessPostData(data)) && (Page.IsCallback == false));
        }
        /// <summary>
        /// Process the Post Data for this control and return true if something has changed.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        protected virtual bool ProcessPostData(NameValueCollection data)
        {   /// this member to be overridden when a control needs to process post data
            return postdata_loaded;
        }

        public virtual void RaisePostDataChangedEvent()
        {   /// since this was not overridden then cancel the request
            Arguments.Cancel = true;
        }

        #endregion

        #region "ICallbackEventHandler Members"
        public string GetCallbackResult()
        {
            string result = null;

            switch (Arguments.ResultData.type)
            {
                case JQNDataTypes.xml:
                    Page.Response.ContentType = "text/xml";

                    result = String.Format("<result><canceled>{0}</canceled><data>{1}</data></result>", Arguments.Cancel, Arguments.ResultData.data);
                    break;
                default:
                    Page.Response.ContentType = "text/x-json";

                    result = JsonConvert.SerializeObject(new
                    {
                        canceled = Arguments.Cancel,
                        type = Enum.GetName(typeof(JQNDataTypes), Arguments.ResultData.type),
                        data = Arguments.ResultData.data
                    }); break;
            }

            return result;
        }

        protected void ParseArguments(string arguments)
        {
            var JSON = JObject.Parse(arguments);

            Arguments = new jQNetArguments(JSON["args"].ToString())
            {
                Event = JSON["event"].Type == JTokenType.String
                    ? (jQNetEvents)Enum.Parse(typeof(jQNetEvents), (string)JSON["event"], true)
                    : JSON["event"].Type == JTokenType.Integer
                        ? ((jQNetEvents)(int)JSON["event"])
                        : jQNetEvents.unknown
            };
        }

        public virtual void RaiseCallbackEvent(string arguments)
        {
            if (Arguments == null)
                ParseArguments(arguments);

            if (postdata_loaded == false)
            {   /// known bug in asp.net under certain conditions the IPostBackDataHandler interface may not have been processed.
                postdata_loaded = LoadPostData(UniqueID, Page.Request.Form);
            }

            if (postdata_loaded && Arguments.Event == jQNetEvents.change)
            {   /// data has changed let's raise the change event. 
                /// we still use the same interface for asp.net.
                RaisePostDataChangedEvent();

                Arguments.IsHandled = true;

                return; /// we are done
            }
            else if (Arguments.Event == jQNetEvents.template)
            {
                Arguments.ResultData.type = JQNDataTypes.html;

                var id = ((string)Arguments["id"]).Substring(1);

                if (!(Arguments.Cancel = RenderTemplates[id] == null))
                {
                    using (StringWriter html = new StringWriter())
                    using (HtmlTextWriter writer = new HtmlTextWriter(html))
                    {
                        RenderTemplates[id].RenderControl(writer);

                        Arguments.ResultData.data = html.ToString().Regex("[\n\r\t]", String.Empty);
                    }
                }

                Arguments.IsHandled = true;

                return; /// we are done
            }
        }
        #endregion

        #region IAttributeAccessor Interface
        string IAttributeAccessor.GetAttribute(string key)
        {
            return Attributes[key];
        }

        void IAttributeAccessor.SetAttribute(string key, string value)
        {
            Attributes[key] = value;
        }
        #endregion
    }
}
